[八股速成]1.基本信息与C++基础

基本信息

好烦,感觉要拿一个基础薄弱、需要系统学习的面评。现在不知道是背面经还是啃书,目前感觉还是要背面经了解个大概。

科普

前端、后端、服务器开发

前端:和用户打交道的,负责用良好的视觉效果将数据呈现给用户,广义的前端包括客户端(安卓、IOS)、Web前端、小程序等。

后端、后台:负责业务逻辑处理。

服务器开发:包含了后台开发,而且也包括支撑整个后台应用的基础开发,比如搜索引擎、微服务、RPC 框架、KV、存储、MQ 等。

正确认识内推

一、基础知识.

C++中this指针与const的纠缠关系

【C++】空指针调用成员函数及访问成员变量

1、基本语言

说一下C++和C的区别

C++实现面向对象和泛型编程,C面向过程。

面向对象:把事物抽象成对象的概念,再让对象执行方法。

面向过程:解决问题时会把问题拆成一个个步骤(函数),然后一步步执行。

泛型编程:函数模板和类模板。重载函数仅仅是类型不同,代码复用率低。

说一下C++中static关键字的作用

  • 修饰变量变为静态变量,静态存储区,在整个程序运行期间一直存在。
  • 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用(函数默认为extern的)。
  • 修饰成员变量,静态数据成员只分配一次内存,供所有对象共用。
  • 修饰成员函数,为静态成员函数,不能调用非静态成员函数和非静态成员数据。

C/C++语言中关于const用法

请你来说一说重载和覆盖

重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。
覆盖:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写。
隐藏:隐藏就是子类会隐藏父类中同名的标识符(不一定是函数),而且如果父类中有同名的标识符,编译通过且不构成覆盖,则一定是构成隐藏关系。

请说一下C/C++ 中指针和引用的区别?

  1. 指针是一个变量,存储着一个地址,指向内存的一个存储单元。引用是原变量的一个别名。
  2. 可以有多级指针,但不能有多级引用。
  3. 指针可以不初始化,引用必须初始化。
  4. 引用的值不可更改。

给定三角形ABC和一点P(x,y,z),判断点P是否在ABC内,给出思路并手写代码

P到ABC构成的三个角之和是否为180°,$\cos A = \dfrac{b^2+c^2-a^2}{2bc}$。

怎么判断一个数是二的倍数,怎么求一个数中有几个1,说一下你的思路并手写代码

  1. 2的倍数,x&(x-1)==0 即为2的倍数。
  2. 用for进行迭代,i&=(i-1) ,相当于每一轮消去最后一个1,看执行多少轮,lowbit x&(-x)也可以。

请你说一说strcpy和strlen

glibc–strcpy源码分析,利用了进程平坦的内存模型,两个指针的差就是两者之间的距离。得到地址间的相对距离,直接将s的每一个字符存入目标数组。

glibc – strlen源码分析,先将char指针地址与int进行字节对齐,然后将char指针强制转为int指针,一次取出4字节的元素,并通过magic_number来判断里面是否有一个字节全部为0,有的话直接用指针地址做差并返回答案。

glibc–memcpy源码分析,内存对齐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void *Memcpy(void *dst, const void *src, size_t size)
{//炮姐的
if (dst == NULL || src == NULL)
return NULL;
char *psrc;
char *pdst;
//地址重叠的情况
if ((src < dst) && (char*)src + size > (char *)dst)
{
psrc = (char*)src + size - 1;
pdst = (char*)dst + size - 1;
while(size--)
{
*pdst-- = *pdst--;
}
}else {
psrc = (char*)src;
pdst = (char*)dst;
while(size--)
{
*pdst++ = *psrc++;
}
}
}

空类包含什么(默认成员函数)

默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、取址运算符、const取址运算符、移动构造函数(C++11)

C++类内可以定义引用数据成员吗?

可以,必须通过成员函数初始化列表初始化。

请你说说你了解的RTTI

Run-Time Type Identification,即运行时类型识别。它可以在程序运行时检查父类型的引用是否可以指向子类型的对象,即确保类型向上转换安全。

请你回答一下C++中拷贝赋值函数的形参能否进行值传递?

可以。但是调用拷贝赋值函数的时候,首先要将实参传递给形参,此时会多调用一次拷贝构造,并不会无限递归。

拷贝构造函数的形参能否进行值传递?

不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。

请你来说一说隐式类型转换

感觉用来写计算几何模板会很有意思,避免的话要在构造函数前加explicit

  1. 使用单参数的构造函数或N个参数中有N-1个是默认参数的构造函数
  2. operator目标类型() const

说说你了解的类型转换

说一说c++中四种cast转换

https://zhuanlan.zhihu.com/p/27966225

  1. static_cast :用于代替各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化(子类向父类)。
  2. dynamic_cast :用于动态类型转换,上行转换(子类向基类)和static_cast没有区别,都是安全的;下行转换时,dynamic_cast会检查转换的类型。
  3. const_cast :用于将const变量转为非const(例如地址转指针)。
  4. reinterpret_cast :什么都可以转,一般不用。

请你说说C语言是怎么进行函数调用的?

C++内存模型:堆,栈,静态全局变量区,常量区,自由存储区。

请你说说C语言参数压栈顺序?

C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。

请你来说一下C++里是怎么定义常量的?常量存放在内存的哪个位置?

静态内存区。const变量位于栈区(函数内)和静态存储区(全局)。

请回答一下数组和指针的区别

  • 把数组作为参数传递的时候,会退化为指针
  • 数组名可作为指针常量
  • 数组是开辟一块连续的内存空间,数组本身的标示符代表整个数组,可以用sizeof取得真实的大小。

请你回答一下野指针是什么?

指向被弃用内存的指针。

请你来说一下函数指针

函数指针是指向函数的指针变量。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。

请你介绍一下C++中的智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

请你说一下你理解的c++中的smart pointer四个智能指针:

  • auto_ptr :C++11已弃用
  • shared_ptr :std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
  • weak_ptr : 解决shared_ptr循环引用问题
  • unique_ptr :unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个unique_ptr。

为什么要用智能指针

为什么要使用智能指针:防止内存泄漏。https://blog.csdn.net/u011475134/article/details/76714243

https://docs.microsoft.com/en-us/cpp/standard-library/memory-functions?view=msvc-160&redirectedfrom=MSDN#make_unique

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

请你回答一下智能指针有没有内存泄露的情况

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

请你来说一下智能指针的内存泄漏如何解决

为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

就我理解感觉像是图论里的树,将平级关系转为父子关系,父节点用shared_ptr指向儿子节点,儿子节点用weak_ptr指向父节点,将图变为DAG。

请你来说一下智能指针shared_ptr的实现

两个成员:裸指针,引用计数器。循环引用中的shared_ptr和weak_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
template<typename T>
class SharedPtr {
public:
int* counter; // 引用计数,用指针表示,多个SharedPtr之间可以同步修改
T* resource; // 裸指针
SharedPtr(T* resc = nullptr) { // 构造函数
cout << __PRETTY_FUNCTION__ << endl;
counter = new int(1);
resource = resc;
}
SharedPtr(const SharedPtr& rhs) { // 拷贝构造函数
cout << __PRETTY_FUNCTION__ << endl;
resource = rhs.resource;
counter = rhs.counter;
++*counter;
}
SharedPtr& operator=(const SharedPtr& rhs) { // 拷贝赋值函数
cout << __PRETTY_FUNCTION__ << endl;
--*counter; // 原来指向的资源的引用计数减1
if (*counter == 0) {
delete counter;
delete resource;
}
resource = rhs.resource;
counter = rhs.counter;
++*counter; // 新指向的资源的引用计数加1
}
~SharedPtr() { // 析构函数
cout << __PRETTY_FUNCTION__ << endl;
--*counter;
if (*counter == 0) {
delete counter;
delete resource;
}
}
int use_count() {
return *counter;
}
};

请你来说一下C++中析构函数的作用

当对象结束其生命周期时,系统自动执行析构函数,释放资源,避免内存泄漏。

请你回答一下为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数

性能问题,有些类被设计为不能被继承,析构函数不是虚函数能减少开销,例如string。

请你来说一下静态函数和虚函数的区别

静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。

请你说一说你理解的虚函数和多态

请你说说虚函数表具体是怎样实现运行时多态的?

请你来回答一下++i和i++的区别

请你来写个函数在main函数执行前先运行

1
2
void before() __attribute__((constructor));
void after() __attribute__((destructor));

请你来回答一下const修饰成员函数的目的是什么?

表明这个函数不会对这个类对象的数据成员(非静态数据成员)作任何改变。

如果同时定义了两个函数,一个带const,一个不带,会有问题吗?

能够构成重载。如果定义的对象是常对象,则调用的是const成员函数,如果定义的对象是非常对象,则调用重载的非const成员函数。

请你来说一说C++函数栈空间的最大值

默认是1M,不过可以调整。

1
#pragma comment(linker, "/STACK:102400000,102400000") //手动扩栈

请你来说一说extern“C”

C++调用C函数需要extern C,因为C语言没有函数重载。

请你回答一下new/delete与malloc/free的区别是什么

new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数。

请你说说C++如何处理返回值

没听懂在问什么

请你说一说select

select在使用前,先将需要监控的描述符对应的bit位置1,然后将其传给select,当有任何一个事件发生时,select将会返回所有的描述符,需要在应用程序自己遍历去检查哪个描述符上有事件发生,效率很低,并且其不断在内核态和用户态进行描述符的拷贝,开销很大

请你说说fork,wait,exec函数

父进程产生子进程使用fork拷贝出来一个父进程的副本,此时只拷贝了父进程的页表,两个进程都读同一块内存,当有进程写的时候使用写实拷贝机制分配内存,exec函数可以加载一个elf文件去替换父进程,从此父进程和子进程就可以运行不同的程序了。fork从父进程返回子进程的pid,从子进程返回0.调用了wait的父进程将会发生阻塞,直到有子进程状态改变,执行成功返回0,错误返回-1。exec执行成功则子进程从新的程序开始运行,无返回值,执行失败返回-1

请你来说一下fork函数

Fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用:

2.编译与底层

请你来说一下一个C++源文件从文本到可执行文件经历的过程?

预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件。
编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件

请你来回答一下include头文件的顺序以及双引号””和尖括号的区别?

当前头文件目录,编译器设置的头文件路径(编译器可使用-I显式指定搜索路径),系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

请你回答一下malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?

malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块;同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址
当进行内存分配时,Malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。

Malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。其中当申请内存小于128K时,会使用系统函数brk在堆区中分配;而当申请内存大于128K时,会使用系统函数mmap在文件映射区分配。

请你说一说C++的内存管理是怎样的?

代码段,只读存储,和文本存储
数据段,存放初始化的全区变量
bss段,存放为初始化的全局变量和局部变量,以及初始化全为0的变量
栈区,存放局部变量,返回值,返回地址,参数值
堆区,动态内存的存放
映射区,mmap的文件映射

请你来说一下C++/C的内存分配

请你回答一下如何判断内存泄漏?

为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。

请你来说一下什么时候会发生段错误

段错误通常发生在访问非法内存地址的时候,具体来说分为以下几种情况:使用野指针、试图修改字符串常量的内容。

请你来回答一下什么是memory leak,也就是内存泄漏

内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的分类:

  1. 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
  2. 系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
  3. 没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

请你来说一下共享内存相关api

Linux允许不同进程访问同一个逻辑内存,提供了一组API,头文件在sys/shm.h中。

请你来说一下reactor模型组成

请自己设计一下如何采用单线程的方式处理高并发

在单线程模型中,可以采用I/O复用来提高单线程处理多个请求的能力,然后再采用事件驱动模型,基于异步回调来处理事件来。

请你说说C++如何处理内存泄漏?

请你说说select,epoll的区别,原理,性能,限制都说一说
请你说一说C++ STL 的内存优化